package koordinator;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

import monitor.Monitor;

import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

import ggt.Koordinator;
import ggt.KoordinatorHelper;
import ggt.KoordinatorPOA;
import ggt.Prozess;
import ggt.Starter;
import ggt.KoordinatorPackage.exAlreadyExists;
import ggt.StarterPackage.exInvalidCount;

public class KoordinatorImpl extends KoordinatorPOA {
	public static final int zuinformierendeProzesse = 3;
	
	private ArrayList<Starter> starterliste = null;
	private Monitor monitor = null;
	private ORB orb = null;
	
	private Prozess[] startProzesse = null;
	private LinkedList<Prozess> aProzessliste = null;
	private ReentrantLock terminierung, lockterminierung;
	private Semaphore warten;
	private int[] startProzesszahl;
	private int prozessanzahl;
	private Random random;
	
	@SuppressWarnings("unused")
	private KoordinatorImpl(){}
	
	public KoordinatorImpl(ORB orb, Monitor monitor){
		this.orb = orb;
		this.monitor = monitor;
		starterliste = new ArrayList<Starter>();
	}
	
	@Override
	public synchronized void registiereProzess(Prozess prozess) throws exAlreadyExists {
		aProzessliste.add(prozess);
		if(aProzessliste.size() == (prozessanzahl-1)){
			warten.release();
		}
	}

	@Override
	public synchronized boolean terminerungAbgeschlossen(Prozess terminator, int Ergebnis) {
		boolean b = false;
		this.lockterminierung.lock();
			b = this.terminierung.tryLock();
		this.terminierung.unlock();
		if(b){
			System.out.println(terminator.name()+ " Ergebnis ist " + Ergebnis);
			return b;
		}else{
			return b;
		}
	}

	@Override
	public void aktiviereStarter(Starter starter, String starterName)
			throws exAlreadyExists {
		this.starterliste.add(starter);
	}

	@Override
	public void entferneStarter(Starter starter) {
		this.starterliste.remove(starter);
	}

	@Override
	public void berechnungStarten(int minProzesse, int maxProzesse,
			int minDelay, int maxDelay, int timeout, int ggt){
		init();
		int anzahlstarter, anzahlProzesseProStarter, startGGT;
		String[] pIDs;
		int[] anfangszahlen;
		Prozess[] ps;
		Koordinator koor = null;
		anzahlstarter = this.starterliste.size();
		anzahlProzesseProStarter = zufall(minProzesse, maxProzesse);
		this.prozessanzahl = anzahlProzesseProStarter * anzahlstarter;
		
		for(int i=0; i < anzahlstarter; i++){
			try {
				this.starterliste.get(i).erstelleProzesse(anzahlProzesseProStarter);
			} catch (exInvalidCount e) {
				e.printStackTrace();
			}
		}
		pIDs = new String[this.prozessanzahl];
		anfangszahlen = new int[this.prozessanzahl];
		ps = new Prozess[this.prozessanzahl];
		
		try {
			POA rootPoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
			rootPoa.the_POAManager().activate();
			koor = KoordinatorHelper.narrow(rootPoa.servant_to_reference(this));
		} catch (Exception e) {
			e.printStackTrace();
		}
		// warten bis sich alle Prozesse bei dem Krodinator gemeldet haben 
		try {
			warten.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//Ring erstellen
		for(int i = this.prozessanzahl-1; i > 0; i--){
			ps[i]= this.aProzessliste.remove(random.nextInt(i));
		}
		ps[0] = this.aProzessliste.removeFirst();
		
		for(int i = 0; i < this.prozessanzahl; i++){
			startGGT = ggt * zufall(1, 100) * zufall(1, 100);
			pIDs[i] = ps[i].name();
			anfangszahlen[i] = startGGT;
			einsortieren(startGGT, ps[i]);
			if(i == 0){
				ps[i].init(ps[i+1], ps[this.prozessanzahl-1], startGGT, zufall(minDelay,maxDelay), timeout, this.monitor, koor);
			}else if( i == this.prozessanzahl-1){
				ps[i].init(ps[0], ps[i-1], startGGT, zufall(minDelay,maxDelay), timeout, this.monitor, koor);
			}else{
				ps[i].init(ps[i+1], ps[i-1], startGGT, zufall(minDelay,maxDelay), timeout, this.monitor, koor);
			}
		}	
		this.monitor.ring(pIDs);
		this.monitor.startzahlen(anfangszahlen);
		sendeStartbefehl();
	}

	@Override
	public Starter[] holestarterListe() {
		Starter s[] = new Starter[starterliste.size()];
		return this.starterliste.toArray(s);
	}

	@Override
	public void quit() {
		while(this.starterliste.size() > 0){
			this.starterliste.get(0).quit();
		}
		orb.shutdown(true);
		
	}
	
	private void init(){
		terminierung = new ReentrantLock(true);
		lockterminierung = new ReentrantLock(true);
		aProzessliste = new LinkedList<Prozess>();
		warten = new Semaphore(0);
		random = new Random();
		startProzesse = new Prozess[zuinformierendeProzesse];
		startProzesszahl = new int[zuinformierendeProzesse];
		for(int i=0; i < zuinformierendeProzesse; i++){
			startProzesszahl[i] = Integer.MAX_VALUE;
		}
	}
	
	private void sendeStartbefehl(){
		for(int i = 0; i < zuinformierendeProzesse; i++){
			this.startProzesse[i].starteBerechung();
		}
	}
	
	private int zufall(int min , int max){
		return random.nextInt(max-min) + min;
	}
	
	private void einsortieren(int ggt, Prozess p){
		for(int i = 0; i < zuinformierendeProzesse; i++){
			if (ggt < this.startProzesszahl[i]){
				for(int j = zuinformierendeProzesse-1; j > i;  j--){
					this.startProzesse[j] = this.startProzesse[j-1];
					this.startProzesszahl[j] = this.startProzesszahl[j-1];
				}
				this.startProzesse[i] = p;
				this.startProzesszahl[i] = ggt;
				return;
			}
		}
	}

}
